/**
 * Copyright (c) 2011 Paul Wagner
 */
package com.pew.tasktimer.domain.report;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.log4j.*;

import com.pew.tasktimer.domain.Task;

/**
 * @author Elwin
 * Created: Mar 31, 2011
 * Purpose: The unit to block Tasks together and
 * sum the hours up as doubles.
 *
 */
@XmlRootElement
public class ReportSumation {

	static Logger cLogger = Logger.getLogger(ReportSumation.class);
	
	@XmlAttribute (name="grouping", required=true)
	private String cStrGrouping = null;
	private boolean cbStrGroupingSet=false;  //simpler test to see if the string was set
	@XmlAttribute (name="group_title")
	private String cStrGroupTitle ="";
	
	private final String CSTR_NOT_SET_GROUPING="N/A";
	
	@XmlElement (name="total_time")
	private double cdSum = 0.0;
	@XmlElement (name="total_tasks")
	private int ciCount = 0;
	
	@XmlElementWrapper(name = "tasks")
	@XmlElement(name = "task")
	private ArrayList<Task> cTasks = null;
	
	@XmlElement(name = "subReport")
	/*
	 * The next level of bins to put a Tasks into.
	 * TODO: Better as a HashTable?
	 */
	private ArrayList<ReportSumation> cReportSums = null;
	/*
	 * Save processing from checking if cReportSums has anything in it.
	 */
	private boolean cbSubReportsSet = false;
	
	/*
	 * Defines what parts of the task to use to form a key.
	 */
	private GROUP_BY ciGroup = GROUP_BY.NOT_SET;
	/*
	 * Gives a predefined set of fields to work with.
	 */
	public static enum GROUP_BY {
		NOT_SET,
		MONTH_YEAR,
		MONTH_DAY_YEAR,
		PROJECT
	}
	
	public ReportSumation() {
		if (cLogger.isDebugEnabled()) {
			cLogger.debug("Contructor new ReportSumation.");
		}
		
		cTasks = new ArrayList<Task>();
	}
	
	public ReportSumation(GROUP_BY myEnum, Task myTask, ArrayList mySubReports) {
		if (cLogger.isDebugEnabled()) {
			cLogger.debug("Constructor with parameters called.");
		}
		this.setGrouping(myEnum);
		this.setGrouping(getGroupingFromTask(myTask));
		if(mySubReports != null && mySubReports.size() > 0)
		{
			
		}
		cTasks = new ArrayList<Task>();
		this.addTask(myTask);
	}
	
	/**
	 * Sets which sets of fields to work with in this bin of tasks;
	 * how to group the tasks in this bin.
	 * @param myEnum
	 */
	public void setGrouping(GROUP_BY myEnum) {
		ciGroup = myEnum;
		switch (ciGroup)
		{
			case MONTH_DAY_YEAR: 	cStrGroupTitle="YYYY-MM-DD";
									break;
			case PROJECT:			cStrGroupTitle="Project";
									break;
			case MONTH_YEAR:		cStrGroupTitle="YYYY-MM";
									break;
			case NOT_SET:
			default:				cStrGroupTitle="Report";
		}
	}
	
	/**
	 * Uses the fields set in this bin to group with to determine what hash key the
	 * given task should use.
	 * @param myTask
	 * @return A string of the fields used; null if no fields set.
	 */
	public String getGroupingFromTask(Task myTask) {
		String lStrReturn = null;
		
		switch(ciGroup) {
			case NOT_SET: 			lStrReturn="";
									break;
			case MONTH_DAY_YEAR: 	lStrReturn = myTask.getCiStopYear() + "-" + myTask.getCiStopMonth() + "-" + myTask.getCiStopDayOfMonth();
								 	break;
			case PROJECT: 			lStrReturn = myTask.getProject();
									break;
			case MONTH_YEAR:		lStrReturn = myTask.getCiStopYear() + "-" + myTask.getCiStopMonth();
									break;
			default:  // leave null
		}
		
		return lStrReturn;
	}
	
	/**
	 * Attempts to add the given task to this bin.
	 * Attempts to add any sub-bins in a recursive manner.
	 * @param myTask
	 * @return true if the task fits this bins group by criteria and was added; false otherwise.
	 */
	public boolean addTask(Task myTask)
	{
		if(!cbStrGroupingSet)
		{
			setGrouping(getGroupingFromTask(myTask));
		}
		//could streamline
		boolean isAccepted = false;
		if(ciGroup == GROUP_BY.NOT_SET)
		{
			// this summation accepts all
			addTaskInternaly(myTask);
			isAccepted = true;
		}
		else
		{
			String lStrTest = getGroupingFromTask(myTask);
			if(lStrTest.equals(cStrGrouping))
			{
				addTaskInternaly(myTask);
				isAccepted = true;
			}
			else
			{
				isAccepted = false;
			}
		}
		return isAccepted;
	}
	
	/**
	 * Adds the task to the internal store and recursively calls sub-bins.
	 * @param myTask
	 */
	private void addTaskInternaly(Task myTask) {
		
		if (cLogger.isDebugEnabled()) {
			cLogger.debug("Adding a task to report: " + this.toString());
		}
		
		cdSum += myTask.getCfDiffHourF();
		ciCount++;
		
		//Add to sub-reports;
		if(cbSubReportsSet)
		{
			if (cLogger.isDebugEnabled()) {
				cLogger.debug("Running Task against sub reports.");
			}
			
			boolean lbSuccessfullyAdded = false;
			for (ReportSumation r : cReportSums) {
				if(r.addTask(myTask))
				{
					lbSuccessfullyAdded = true;
					break;
				}
			}
			
			
			//Create a new bin for the task with a new grouping key
			if(!lbSuccessfullyAdded){
				//No match was found.
				if (cLogger.isDebugEnabled()) {
					cLogger.debug("No sub bin was found for this Task. Creating a new sub bin.");
					
				}
				ReportSumation lReport = (ReportSumation) this.cReportSums.get(0);
				ReportSumation newReport = this.addReportSumation(lReport);
				newReport.addTask(myTask);
			}
		}
		else{
			cTasks.add(myTask);
		}
	}
	
	/**
	 * Sets the String used as a group by and has key to judge if Tasks belong in this bin.
	 * @param myGrouping
	 */
	public void setGrouping(String myGrouping)
	{
		if (cLogger.isDebugEnabled()) {
			cLogger.debug("Setting grouping to: " + myGrouping);
		}
		cStrGrouping = myGrouping;
		cbStrGroupingSet = true;
	}
	
	public String getGrouping() {
		return cStrGrouping;
	}
	
	public GROUP_BY getGroupBy() {
		return ciGroup;
	}
	
	/**
	 * Add a sub report to this bin.  This should typically narrow down the
	 * tasks to be added together more.  Adds only the reports criteria,
	 * not any of the tasks already associated with it.
	 * @param myReport
	 */
	public ReportSumation addReportSumation(ReportSumation myReport) {
		
		ReportSumation lReport = null;
		
		if (cReportSums == null) {
			cReportSums = new ArrayList<ReportSumation>();
		}
		else
		{
			for(ReportSumation r :cReportSums){
				if(myReport.getGroupBy() == r.getGroupBy()
						&& r.getGrouping() == null) {
					lReport = r;
					break;
				}
			}
		}
		
		if (cLogger.isDebugEnabled()) {
			cLogger.debug("Adding sub report: " + myReport.toString() + " to report: " + this.toString());
		}
		
		
		if(lReport == null)
		{
			lReport = new ReportSumation();
			lReport.setGrouping(myReport.getGroupBy());
			cReportSums.add(lReport);
		}
		
		lReport.addReportSumations(myReport.getReportSumations());
		cbSubReportsSet = true;
		
		return lReport;
	}
	
	public void addReportSumations(ArrayList<ReportSumation> mySumations){
		if(mySumations != null && mySumations.size() > 0) {
			if (cLogger.isDebugEnabled()) {
				cLogger.debug("Recieved a set of reports to add.");
			}
			
			for (ReportSumation r : mySumations) {
					this.addReportSumation(r);
			}
		}
	}
	
	public ArrayList<ReportSumation> getReportSumations() {
		if(cReportSums != null && cReportSums.size() > 0)
		{
			return this.cReportSums;
		}
		
		return null;
	}
	
	
	/*
	 * Getters and Setters
	 */
	
	

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer lStrBuf = new StringBuffer("ReportSumation [cStrGroupTitle=" + cStrGroupTitle
				+ ", cStrGrouping=" + cStrGrouping + ", cbSubReportsSet="
				+ cbSubReportsSet + ", cdSum=" + cdSum + ", ciCount=" + ciCount
				+ ", ciGroup=" + ciGroup + "]");
		
		if(cReportSums != null)
		{
			lStrBuf.append("\nContains: ");
			for (ReportSumation r : cReportSums)
			{
				lStrBuf.append("\n" + r.toString());
			}
		}
		
		return lStrBuf.toString();
	}

	/**
	 * @return the cdSum
	 */
	public double getCdSum() {
		return cdSum;
	}

	/**
	 * @return the ciCount
	 */
	public int getCiCount() {
		return ciCount;
	}
	
	
}
